<?php
// @formatter:off
/**
 * @file WCContainer.abs.php
 * @author Alejandro Dario Simi
 * @date $Date: 2013-07-11 21:02:48 +0000 (Thu, 11 Jul 2013) $
 *
 * $Id: WCContainer.abs.php 95 2013-07-11 21:02:48Z daemonraco@gmail.com $
 * $URL: http://wcomix.googlecode.com/svn/branches/wcomix-1.0/includes/containers/WCContainer.abs.php $
 */
// @formatter:on

/** @todo class WCContainer must be abstract. */

/**
 * @abstract
 * @class WCContainer
 * This class represents a container for objects managed by certain
 * WCBasicHolder. It logic allows to access this objects in many way, like by id,
 * by name and others.
 */
class WCContainer {
	/**
	 * @var WCHolder This is the pointer to the objects holder to use.
	 */
	protected $_holder = false;
	/**
	 * @var int Holds the count of items inside the container.
	 */
	protected $_count = 0;
	/**
	 * @var WCRepresentation[int] This is the list of held objects.
	 */
	protected $_items = array();
	/**
	 * @var int[][string] List of names. Each name has it's own list of id
	 * matching the same name.
	 */
	protected $_names = array();
	/**
	 * @var boolean This flag indicates if there are element awaiting to be
	 * loaded.
	 */
	protected $_unloadedItems = false;
	/**
	 * @var boolean This flag indicates if elements with issues must be
	 * loaded.
	 */
	protected $_loadInvalids = false;
	//
	// Public methods.
	/**
	 * This method adds an element to the container, since it is only an ID,
	 * the real object will be loaded only when required.
	 *
	 * @param int $id Id of the element to be added.
	 */
	public function addId($id) {
		//
		// Enforce integer value.
		$id += 0;
		//
		// Checking that the id is not already added.
		if(!isset($this->_items[$id])) {
			//
			// Adding the id as false to delay the loading of the real
			// object.
			$this->_items[$id] = false;
			//
			// 'Cause it was added, as false, there at least one
			// unloaded item.
			$this->_unloadedItems = true;
			//
			// Internal count must be incremented.
			$this->_count++;
		}
	}
	/**
	 * This method allows to add a fully loaded element.
	 *
	 * @param int $id Id to associate with the element.
	 * @param WCRepresentation $item Fully loaded element to add.
	 */
	public function addItem($id, &$item) {
		//
		// Enforce integer value.
		$id += 0;
		//
		// Checking that the id and the item's id match and it's not
		// already added.
		// Also, the element to be added should not have issues, unless
		// the flag for _loadInvalids is on.
		if($id == $item->id() && !isset($this->_items[$id]) && ($item->ok() || $this->_loadInvalids)) {
			//
			// Adding the id associated with the item, this won't
			// require a delayed loading of the object.
			$this->_items[$id] = $item;
			//
			// Adding the item into name lists.
			$this->addItemToNames($id);
			//
			// Internal count must be incremented.
			$this->_count++;
		}
	}
	/**
	 * This method allows to know how many items are being held in this
	 * container.
	 *
	 * @return int Returns the amount of items.
	 */
	public function count() {
		return $this->_count;
	}
	/**
	 * This method allows to get all item ids held in this container.
	 *
	 * @return int[] Returns the list of ids.
	 */
	public function ids() {
		return array_keys($this->_items);
	}
	/**
	 * This method allows to obtain an element of this container based on its
	 * ID.
	 *
	 * @param int $id Item id to look for
	 * @return WCRepresentation Returns a loaded element matching the id.
	 * Otherwise, returns null.
	 */
	public function item($id) {
		//
		// Triggering the full load of the element.
		$this->loadItem($id);
		//
		// If present, returning the element.
		return isset($this->_items[$id]) ? $this->_items[$id] : null;
	}
	/**
	 * This methods allows to get full list of objects been held by this
	 * container.
	 *
	 * @return WCRepresentation[] Returns a list of fully loaded elements.
	 * When no elements, it returns an empty list.
	 */
	public function items() {
		//
		// Loading all the element that haven't been loaded yet.
		$this->loadUnloadedItems();
		//
		// Returning the list.
		return $this->_items;
	}
	/**
	 * This method allows to obtain an element based on its name.
	 *
	 * @param string $name Name to look for.
	 * @return int Returns the id of an element that matches the name. If
	 * there are more than one element matching the name, only one is returned
	 * an there's no warranty it'll be the same on each execution. Otherwise,
	 * it returns null.
	 */
	public function name($name) {
		//
		// Loading all the element that haven't been loaded yet.
		$this->loadUnloadedItems();
		//
		// If there's a list for the name it has at least one element, it
		// returns the element id.
		return isset($this->_names[$name]) && count($this->_names[$name]) > 0 ? array_shift(array_values($this->_names[$name])) : null;
	}
	/**
	 * This method allows to obtain a list of element ids based on its name.
	 *
	 * @param string $name Name to look for.
	 * @return int[] Returns a list of ids of elements matching the name.
	 * Otherwise, it returns an empty list.
	 */
	public function nameList($name) {
		//
		// Loading all the element that haven't been loaded yet.
		$this->loadUnloadedItems();
		//
		// Checking if there's a list for the name and returning it.
		return isset($this->_names[$name]) ? $this->_names[$name] : array();
	}
	/**
	 * This method allows to get a list of all loaded names.
	 *
	 * @return string[] Returns a list of names.
	 */
	public function names() {
		//
		// Loading all the element that haven't been loaded yet.
		$this->loadUnloadedItems();
		//
		// Based on currently loaded names, it returns the keys of such
		// list.
		return array_keys($this->_names);
	}
	/**
	 * This method allows to remove an element from the container.
	 *
	 * @param int $id ID of the element to be removed.
	 */
	public function removeId($id) {
		//
		// Enforce integer value.
		$id += 0;
		//
		// If it doesn't exists, it wasn't added. Then no action is
		// required.
		if(isset($this->_items[$id])) {
			//
			// If is different than false, it may appear on names'
			// lists.
			if($this->_items[$id] !== false) {
				//
				// Searching for the name with the id.
				foreach($this->_names as $name => $ids) {
					//
					// Checking if the current name has the id.
					if(in_array($id, $ids)) {
						//
						// Removing the id.
						$this->_names[$name] = array_diff($this->_names[$name], array($id));
						//
						// When found this loop stops.
						break;
					}
				}
			}
			//
			// Removing it from items list.
			unset($this->_items[$id]);
			//
			// Decreasing the count of elements.
			$this->_count--;
		}
	}
	/**
	 * This method allows to restart a container, which means to remove all
	 * its elements and leave it as if it was never used.
	 */
	public function reset() {
		//
		// Removing all cached elements.
		// @{
		$this->_items = array();
		$this->_names = array();
		// @}
		//
		// Setting the unloaded elements flag to off, because there no
		// elements.
		$this->_unloadedItems = false;
		//
		// Also, the count of elements turns zero.
		$this->_count = 0;
	}
	//
	// Protected methods.
	/**
	 * This method adds a loaded element to the name's lists.
	 * Internally, each name represents a list of ids of element that match
	 * the name.
	 *
	 * @param int $id ID of the element to load.
	 */
	protected function addItemToNames($id) {
		//
		// Only when the object is ok, it is added to names lists.
		// @warning At this point item is supposed to be loaded.
		if($this->_items[$id]->ok()) {
			//
			// Getting the item's name.
			$name = $this->_items[$id]->name();
			//
			// If there's no list for the name, it is created.
			if(!isset($this->_names[$name])) {
				$this->_names[$name] = array();
			}
			//
			// Adding the id to the list
			$this->_names[$name][] = $id;
		}
	}
	/**
	 * This is a basic constructor specification and it may be used by
	 * inherited classes.
	 *
	 * @param WCHolder $holder Holder in charge of loading all elements.
	 * @param boolean $invalids This parameter activates the loading of
	 * elements with issues.
	 */
	protected function basicConstructor(WCHolder &$holder, $invalids = false) {
		$this->_holder = $holder;
		$this->_loadInvalids = $invalids;
	}
	/**
	 * This is a basic destructor specification and it may be used by
	 * inherited classes.
	 */
	protected function basicDestructor() {
		$this->_items = array();
		$this->_names = array();
	}
	/**
	 * This method loads all those elements that haven't been loaded yet.
	 */
	protected function loadUnloadedItems() {
		//
		// Checking if the flag for unloaded elements is on, otherwise,
		// this step is ignored.
		if($this->_unloadedItems) {
			//
			// Searching through every element.
			foreach($this->_items as $id => $value) {
				//
				// Checking of the element is not yet loaded.
				if($value === false) {
					//
					// Loading the element.
					$this->loadItem($id);
				}
			}
			//
			// Updating the count of elements
			$this->_count = count($this->_items);
			//
			// Setting the unloaded elements flag to off.
			$this->_unloadedItems = false;
		}
	}
	/**
	 * This method triggers the real loading of an element.
	 *
	 * @param int $id ID of the element to load.
	 */
	protected function loadItem($id) {
		//
		// Checking that it's added and not yet loaded. Otherwise, nothing
		// is done.
		if(isset($this->_items[$id]) && $this->_items[$id] === false) {
			//
			// Asking the items holder to get certain item.
			$aux = $this->loadItemFromHolder($id);
			//
			// Checking that the loaded element exists and it has no
			// issues.
			// if it exists but with issues, the container can be
			// forced to load it through the flag _loadInvalids.
			if($aux && ($aux->ok() || $this->_loadInvalids)) {
				//
				// Replacing current item value in the list by the
				// actual object.
				$this->_items[$id] = $aux;
				//
				// Adding the element in the name's list.
				$this->addItemToNames($id);
			} else {
				//
				// If the element can't be added, it shouldn't be
				// on any list, therefore it is removed.
				unset($this->_items[$id]);
				//
				// And the count of elements is decreased.
				$this->_count--;
			}
		}
	}
	/**
	 * @abstract
	 * This abstract method asks to the element's holder for certain element
	 * based on its id.
	 *
	 * @param int $id ID of the element to requested.
	 */
	protected function loadItemFromHolder($id) {
		//
		// Since this is an abstract method, any attempt to called will
		// cause a fatal failure. It must be inherited and redefined.
		die(__CLASS__."::".__FUNCTION__."(): Subclass Responsibillity.");
		return false;
	}
}
?>